package com.example.doc_searcher.searcher;

import com.example.doc_searcher.model.DocInfo;
import com.example.doc_searcher.model.Weight;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2025-01-28
 * Time: 15:04
 */
public class Index {
    private static String INDEX_PATH = null;

    static{
        if(Config.isOnline){
            INDEX_PATH = "/root/install/doc_searcher_index/";//线上路径
        }else {
            INDEX_PATH = "D:\\doc_searcher_index/";//鲁棒性~序列化后索引文件放的地方
        }
    }
    private ObjectMapper objectMapper = new ObjectMapper();
    //使用数组下标来表示docId,（前置索引）
    private ArrayList<DocInfo> forwardIndex = new ArrayList<>();

    //使用哈希表来表示倒排索引，key是词，value是一组跟这个词相关联的文章（倒排索引）
    private HashMap<String,ArrayList<Weight>> invertedIndex = new HashMap<>();

    //创建两个锁对象
    private Object locker1 = new Object();
    private Object locker2 = new Object();

    //1：给定docId，在正排索引中，查询文档详细信息
    public DocInfo getDocInfo(int docId){
        return forwardIndex.get(docId);//docId就对应集合中元素的下标
    }

    //2：在倒排索引中，查询哪些文档和这个词相关联，
    // 不同文档，相关联的程度是不一样的，所以我们用集合来作为返回值
    public List<Weight> getInverted(String term){
        return invertedIndex.get(term);
    }

    //3：新增文档
    public void addDoc(String title , String url , String content){
        //新增文档操作，正排、倒排索引都需要更新
        //1：构建正排索引
        DocInfo docInfo = buildForward(title,url,content);
        //2：构建倒排索引
        buildInverted(docInfo);
    }
    private void buildInverted(DocInfo docInfo){
        class WordCnt{
            //统计这个词在标题和正文中出现的次数，标题权重大
            public int titleCount;
            public int contentCount;
        }
        //map来统计词频
        HashMap<String,WordCnt> wordCntHashMap = new HashMap<>();

        //1:对标题分词
        List<Term> terms = ToAnalysis.parse(docInfo.getTitle()).getTerms();
        //2:遍历，统计词频
        for(Term term : terms){
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);//map中不存在这个key，就返回null

            if(wordCnt == null){
                WordCnt newWordCnt = new WordCnt();
                newWordCnt.titleCount = 1;
                newWordCnt.contentCount = 0;
                wordCntHashMap.put(word,newWordCnt);
            }else {
                wordCnt.titleCount += 1;
            }
        }
        //3：对正文进行词频统计
        terms = ToAnalysis.parse(docInfo.getContent()).getTerms();//集合重复利用
        for(Term term : terms){
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                WordCnt newWordCnt = new WordCnt();
                newWordCnt.titleCount = 0;
                newWordCnt.contentCount = 1;
                wordCntHashMap.put(word,newWordCnt);
            }else {
                wordCnt.contentCount += 1;
            }
        }
        //把wordCntHashMap中存储的结果汇总到一个Map中
        //文档的权重 =  标题中出现的次数 * 10 + 正文中出现的次数
        //在遍历这个Map，更新倒排索引中的结构
        for(Map.Entry<String,WordCnt> entry : wordCntHashMap.entrySet()){
            synchronized (locker2){
                List<Weight> invertedList = invertedIndex.get(entry.getKey());//去倒排索引中看是否已经存在当前词
                if(invertedList == null){
                    ArrayList<Weight> newInvertedList = new ArrayList<>();
                    Weight weight = new Weight();
                    weight.setDocId(docInfo.getDocId());
                    weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);
                    newInvertedList.add(weight);
                    invertedIndex.put(entry.getKey(),newInvertedList);
                }else {
                    Weight weight = new Weight();
                    weight.setDocId(docInfo.getDocId());
                    weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);
                    invertedList.add(weight);//倒排索引中已经出现了这个词的话，就直接添加到value（集合）中去
                }
            }
        }
    }

    private DocInfo buildForward(String title , String url , String content){
        DocInfo docInfo = new DocInfo();
        docInfo.setTitle(title);
        docInfo.setUrl(url);
        docInfo.setContent(content);
        synchronized(locker1){//对index加锁
            docInfo.setDocId(forwardIndex.size());//新加入的文档id就是正排索引集合的长度
            forwardIndex.add(docInfo);
        }
        return docInfo;
    }


    //4：把内存中的索引结构保存到磁盘中
    public void save(){
        long beg = System.currentTimeMillis();
        //使用两个文件分别保存正排和倒排
        //1：判定索引对应目录是否存在，不存在就创建
        File indexPathFile = new File(INDEX_PATH);
        if(!indexPathFile.exists()){
            boolean isCreated = indexPathFile.mkdirs();//以防有多层目录
            if (!isCreated) {
                System.err.println("目录创建失败: " + INDEX_PATH);
                return;
            }
        }
        File forwardIndexFile = new File(INDEX_PATH + "forward.txt");
        File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");
        //将对象转化为json对象，就是一堆字符串
        try {
            objectMapper.writeValue(forwardIndexFile,forwardIndex);//将对象转化为字符串，在写入文件。两步合一
            objectMapper.writeValue(invertedIndexFile,invertedIndex);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("保存索引完毕! 消耗时间：" + (end - beg) + "ms");
    }

    //5:把磁盘中的索引数据加载到内存中去
    public void load() throws IOException {
        long beg = System.currentTimeMillis();
        System.out.println("加载索引开始");
        //1:设置加载索引路径
        File forwardIndexFile = new File(INDEX_PATH+"forward.txt");
        File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");
        forwardIndex = objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});
        invertedIndex = objectMapper.readValue(invertedIndexFile, new TypeReference< HashMap<String,ArrayList<Weight>> >() {});
        long end = System.currentTimeMillis();
        System.out.println("加载索引结束！消耗时间为：" + (end - beg) + "ms");
    }

    public static void main(String[] args) throws IOException {
        Index index = new Index();
        index.load();
        System.out.println("加载索引完成");
    }
}
